package jmind.core.aspect;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import jmind.base.cache.ICache;
import jmind.base.cache.LocalCache;
import jmind.base.util.DataUtil;
import jmind.base.util.GlobalConstants;
import jmind.base.annotation.CacheMonitor;
import jmind.core.cache.support.GuavaCache;
import jmind.core.geo.GLocation;
import jmind.core.log.LogUtil;
import jmind.core.manager.XMemCacheManager;
import jmind.core.support.MethodSupport;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * http://weiboxie.iteye.com/blog/1985209
 * 基于SpEL 解析key 的缓存形式
 * spel #表示参数对象， @可以引用spring注解对象 #root表示跟对象，#this表示本身。还可以用 < > and or 等等
 * @CacheMonitor(name = "cacheName", prefix = "userid",key="#user.cid,#name",exp=3600)
 * @CacheMonitor(name = "cacheName", key = "'userid_'+#userId")
 * @author wbxie
 * @see CacheAspectSupport http://sishuok.com/forum/blogPost/list/2463.html
 *  http://www.cnblogs.com/rollenholt/p/4202631.html
 * @see Cacheable
 * @see CacheEvict
 * 扫描表达式配置 http://www.cnblogs.com/leiOOlei/p/3613352.html
 * https://www.cnblogs.com/itsoku123/p/10744244.html
 * spring aop 这种实现方式有内部调用问题，即类本身调用aop的方法不会被aop 
 * 解决方案参考 http://blog.csdn.net/wxwzy738/article/details/27566645
1、增加配置
<aop:aspectj-autoproxy proxy-target-class="true" />
<aop:config expose-proxy="true"  />
 2、修改我们的业务实现类
this.b();-----------修改为--------->((AService) AopContext.currentProxy()).b();

3、
或者实现 自定义BeanSelfAware 接口。配置 扫描 InjectBeanSelfProcessor类
 * 2013-12-4
 */
//@Component
//  @Aspect
public abstract class CacheSpELAspect {

    public abstract ICache getCache(ICache.Type type);

    final Logger logger = LoggerFactory.getLogger(getClass());

    public final Map<Method, Class> M = Maps.newConcurrentMap();
    final ExpressionParser parser = new SpelExpressionParser();
    final ConcurrentMap<String, List<Expression>> expressionMap = Maps.newConcurrentMap();


    private final List<Expression> getExpressions(String spel) {
        List<Expression> list = expressionMap.get(spel);
        if (list == null) {
            list = Lists.newArrayList();
            StringTokenizer token = new StringTokenizer(spel, GlobalConstants.COMMA);
            while (token.hasMoreTokens()) {
                list.add(parser.parseExpression(token.nextToken()));
            }
            expressionMap.put(spel, list);
        }
        return list;

    }


    @Around(value = "execution(* *(..)) && @annotation(monitor)", argNames = "pjp,monitor")
    public Object doAround(ProceedingJoinPoint pjp, CacheMonitor monitor) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        ICache cache = getCache(monitor.type());
        if (cache == null) {
            logger.warn(monitor.type() + " cache is null " + signature.toShortString());
            return pjp.proceed();
        }
//        final String key = monitor.key().isEmpty() ? DataUtil.composeKey(monitor.prefix(), pjp.getArgs()) : monitor.prefix()
//                + GlobalConstants.DASH + MethodSupport.getProperty(pjp.getArgs()[0], monitor.key());

        String key;
        if (monitor.key().isEmpty()) {
            Object[] values = pjp.getArgs();
            // 如果入参只有一个且为Collection, 返回值为Map, 则调用collectionCache方法,支持 多key 查询
            if (values.length == 1 && values[0] instanceof Collection && Map.class.isAssignableFrom(signature.getReturnType())) {
                return collectionCache(pjp, monitor, signature, cache, values);
            }
            key = DataUtil.composeKey(monitor.prefix(), pjp.getArgs());
        } else {
            StandardEvaluationContext context = new StandardEvaluationContext();
            String[] names = signature.getParameterNames(); // 参数变量名
            Object[] values = pjp.getArgs(); // 参数值
            for (int i = 0; i < names.length; i++) {
                context.setVariable(names[i], values[i]);
            }

            StringBuilder sb = new StringBuilder(monitor.prefix());
            List<Expression> expressions = getExpressions(monitor.key());
            for (Expression exp : expressions) {
                sb.append(GlobalConstants.DASH).append(exp.getValue(context));
            }
            key = sb.toString();
        }


        //添加参数值 做为key
        if (monitor.remove()) {
            Object object = pjp.proceed();
            cache.delete(key);
            return object;
        }

        Object obj = cache.get(key);
        if (obj == null) { // 缓存不存在
            obj = pjp.proceed();
            if (obj != null) {
                cache.set(key, monitor.exp(), obj);
            }
            return obj;
        }

        if (monitor.type() == ICache.Type.REDIS) {
            return parseRedisValue(signature, obj.toString());
        }
        return obj;

    }

    private final Object parseRedisValue(final MethodSignature signature, String obj) throws Throwable {
        Class returnType = signature.getReturnType();
        if (returnType.equals(String.class)) { // String 直接返回
            return obj;
        } else if (List.class.isAssignableFrom(returnType)) { // List 特许处理
            List<?> list = JSON.parseArray(obj, getRawType(signature, returnType, 0));
            //  不是接口，并且不是arraylist实例
            if (!returnType.isInterface() && !returnType.isInstance(list)) {
                List o = (List) returnType.newInstance();
                o.addAll(list);
                return o;
            }
            return list;
        } else if (Set.class.isAssignableFrom(returnType)) {
            List list = JSON.parseArray(obj, getRawType(signature, returnType, 0));
            if (returnType.isInterface()) {
                return new HashSet<>(list);
            }
            Set set = (Set) returnType.newInstance();
            set.addAll(list);
            return set;
        } else if (Map.class.isAssignableFrom(returnType)) {
            // if(aClass.equals(String.class) || Number.class.isAssignableFrom(aClass))
            Map map = JSON.parseObject(obj, Map.class);
            Class aClass = getRawType(signature, returnType, 1);
            //  这里判断不是基础类型，需要单个继续转换
            if (!map.isEmpty() && map.values().iterator().next().getClass() != aClass) {
                Map mm = newMap(aClass);
                map.forEach((k, v) -> mm.put(k, JSON.parseObject(v.toString(), aClass)));
                return mm;
            }
            return map;
        }
        // Primitive,Number,Map,List,set 都能支持
        return JSON.parseObject(obj, signature.getReturnType());

    }

    private final <T> Map<String, T> newMap(Class<T> clazz) {
        return new HashMap<String, T>();
    }

    /**
     * 代码描述 : 入参为Collection时的缓存方法
     *
     * @param values 切入的方法的入参
     *               date : 2018/7/25
     *               modified by : [变更日期YYYY-MM-DD][更改人姓名][变更描述]
     * @author subo177693
     */
    private Map collectionCache(ProceedingJoinPoint pjp, CacheMonitor monitor, MethodSignature signature, ICache cache, Object[] values)
            throws Throwable {
        //获取入参collection
        Collection inputCollection = (Collection) values[0];
        // key的集合
        Collection<String> keyCollection = new ArrayList<>(inputCollection.size());
        // key与入参的对应关系
        Map<Object, String> inputAndkeyRelation = new HashMap<>(inputCollection.size());
        for (Object o : inputCollection) {
            if (o != null && !inputAndkeyRelation.containsKey(o)) {
                String singleKey = DataUtil.composeKey(monitor.prefix(), o);
                inputAndkeyRelation.put(o, singleKey);
                keyCollection.add(singleKey);
            }
        }
        // 从缓存获取有缓存的map
        Map<String, Object> multiResult = cache.getMulti(keyCollection);
        // 有缓存的结果map
        Map cacheResult = new HashMap(inputCollection.size());
        Collection inputCollectionNew = newColection(inputCollection);
        for (Object o : inputCollection) {
            Object cacheValue = multiResult.get(inputAndkeyRelation.get(o));
            if (cacheValue != null) {
                cacheResult.put(o, cacheValue);
            } else {
                inputCollectionNew.add(o);
            }
        }
        //  redis 需要转换一下
        if (monitor.type() == ICache.Type.REDIS && !cacheResult.isEmpty()) {
            Class aClass = getRawType(signature, signature.getReturnType(), 1);
            if (cacheResult.values().iterator().next().getClass() != aClass) {
                cacheResult.forEach((k, v) -> cacheResult.put(k, JSON.parseObject(v.toString(), aClass)));
            }
        }

        // 入参去除有缓存的input参数
        if (inputCollectionNew.isEmpty()) {
            return cacheResult;
        }
        // 重新赋值入参
        values[0] = inputCollectionNew;
        // 对剩余的input参数执行方法
        Map<Object, Object> noCacheResult = (Map) pjp.proceed(values);
        for (Map.Entry entry : noCacheResult.entrySet()) {
            cache.set(inputAndkeyRelation.get(entry.getKey()), monitor.exp(), entry.getValue());
        }
        // 合并结果
        cacheResult.putAll(noCacheResult);
        return cacheResult;
    }


    private Collection newColection(Collection inputCollection) {
        Class<? extends Collection> aClass = inputCollection.getClass();
        if (aClass.isInterface()) {
            return inputCollection instanceof Set ? new HashSet() : new ArrayList();
        }
        try {
            return aClass.newInstance();
        } catch (Exception e) {
            return inputCollection instanceof Set ? new HashSet() : new ArrayList();
        }
    }


    private final Class getRawType(MethodSignature signature, Class returnType, int index) {
        return M.computeIfAbsent(signature.getMethod(), m -> {
            java.lang.reflect.Type type = m.getGenericReturnType();
            TypeToken<?> type2 = TypeToken.of(type).resolveType(returnType.getTypeParameters()[index]);
            return type2.getRawType();
        });

    }

    /**
     * SpEL 解析列子
     * org.springframework.cache.interceptor.ExpressionEvaluator
     * http://sishuok.com/forum/blogPost/list/2463.html
     * 2013-12-4
     */
      void expression() {
        ExpressionParser parser = new SpelExpressionParser();
        //1.给root对象赋值 
        GLocation root = new GLocation();
        root.setCity("湖南");
        root.setX(101);

        GLocation user = new GLocation();
        user.setCity("beijing");
        user.setX(12);
        StandardEvaluationContext context = new StandardEvaluationContext(root);
        context.setVariable("userId", 101);
        context.setVariable("user", user);
        Object value = parser.parseExpression("#userId + 'findById'").getValue(context);
        System.out.println(value);

        value = parser.parseExpression("#user.city").getValue(context);
        System.out.println(value);

        String result1 = parser.parseExpression("#root.city").getValue(context, String.class);
        System.out.println(result1);

        Object result = parser.parseExpression("#this").getValue(context);
        System.out.println(result);

    }


}
